package com.apes.scm.stock.profitAndLoss;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.apes.framework.api.DomainService;
import com.apes.framework.api.SpringManager;
import com.apes.framework.jpa.Model;
import com.apes.framework.jpa.generator.service.SequenceService;
import com.apes.framework.jpa.logger.ApesLogger;
import com.apes.framework.jpa.logger.ApesLoggerRepository;
import com.apes.framework.jpa.repository.CustomRepository;
import com.apes.framework.jpa.spec.JpaDsl;
import com.apes.framework.jpa.spec.model.Group;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.plugin.outbox.OutboxPayloadWrapper;
import com.apes.framework.plugin.outbox.OutboxSubscriberService;
import com.apes.framework.plugin.outbox.annotation.OutboxSubscriber;
import com.apes.framework.util.MapUtil;
import com.apes.scm.account.account.model.AccountingMethod;
import com.apes.scm.account.account.model.InventoryBatch;
import com.apes.scm.account.account.model.InventoryMode;
import com.apes.scm.account.account.model.Stock;
import com.apes.scm.account.account.repository.InventoryBatchRepository;
import com.apes.scm.account.account.repository.StockRepository;
import com.apes.scm.account.account.service.InventoryService;
import com.apes.scm.account.account.service.ProductAccountService;
import com.apes.scm.account.tax.model.Tax;
import com.apes.scm.account.tax.service.TaxService;
import com.apes.scm.invoice.model.Invoice;
import com.apes.scm.invoice.model.InvoiceItem;
import com.apes.scm.invoice.service.InvoiceService;
import com.apes.scm.masterdata.party.model.PartyRole;
import com.apes.scm.masterdata.party.repository.PartyRoleRepository;
import com.apes.scm.rbac.repository.UserRepository;
import com.apes.scm.voucher.model.Voucher;
import com.apes.scm.voucher.model.VoucherItem;
import com.apes.scm.voucher.repository.VoucherRepository;
import com.apes.scm.voucher.service.BusinessType;
import com.apes.scm.voucher.service.VoucherService;
import com.apestech.framework.util.StringUtil;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
public class ProductLossService extends DomainService {

    @Autowired
    StockRepository stockRepository;

    @Autowired
    InventoryBatchRepository inventoryBatchRepository;

    @Autowired
    ProductAccountService productAccountService;

    @Autowired
    ProfitAndLossRepository profitAndLossRepository;

    @Autowired
    InventoryService inventoryService;

    @Autowired
    private SequenceService sequenceService;
    @Autowired
    private PartyRoleRepository partyRoleRepository;

    @Autowired
    private ApesLoggerRepository apesLoggerRepository;

    /**
     * 创建
     *
     * @param request
     * @return
     */
    public ProfitAndLoss create(SimpleRequest request) {
        ProfitAndLoss profitAndLoss = request.getO(ProfitAndLoss.class);
        profitAndLoss.setState("sent");
        profitAndLoss.setCreator(request.getSession().getUserId());
        String companyId = request.getCurrentCompany() == null ? "" : ((PartyRole)request.getCurrentCompany()).getId();
        profitAndLoss.setId("BS" + companyId + sequenceService.getId(ProfitAndLoss.CONST_TABLE_NAME));
        ProfitAndLoss newProfitAndLoss = profitAndLossRepository.saveAndFlush(profitAndLoss);
        newProfitAndLoss.getProfitAndLossItems().stream().forEach(
                item -> {
                    item.setQty(Math.abs(item.getQty()));
                    // 可用数减少
                    setProductAvailable(newProfitAndLoss, item, -1, "报损单创建");

                    // 批次冻结数增加
                    setInventoryBatch(item, 1);
                });
        return newProfitAndLoss;
    }

    /**
     * 功能: 修改
     *
     * @param request
     * @return
     */
    public ProfitAndLoss update(SimpleRequest request) {
        isVaild(request, profitAndLossRepository);
        ProfitAndLoss newProfitAndLoss = request.getO(ProfitAndLoss.class);

        // 还原之前占用的
        ProfitAndLoss oldProfitAndLoss = profitAndLossRepository.findOne(newProfitAndLoss.getId());
        oldProfitAndLoss.getProfitAndLossItems().stream().forEach(
                item -> {
                    // 可用数还原
                    setProductAvailable(oldProfitAndLoss, item, 1, "报损单更新还原");

                    // 批次冻结数还原
                    setInventoryBatch(item, -1);
                });

        // 重新写入，因为存在修改的时候添加，导致新产生的明细是没有 id 的
        ProfitAndLoss persistProfitAndLoss = profitAndLossRepository.saveAndFlush(newProfitAndLoss);
        persistProfitAndLoss.getProfitAndLossItems().stream().forEach(
                item -> {
                    // 可用数减少
                    setProductAvailable(persistProfitAndLoss, item, -1, "报损单创建");

                    // 批次冻结数增加
                    setInventoryBatch(item, 1);
                });
        return persistProfitAndLoss;
    }

    /**
     * findOne加强版（权限过滤）
     * @param request
     * @return
     */
    public ProfitAndLoss findOnePro(SimpleRequest request){
        Group group = JpaDsl.getGroup(request);
        List<ProfitAndLoss> profitAndLosses = profitAndLossRepository.findAll(group.build(true));
        if (profitAndLosses.size() == 0) return null;
        Optional<ProfitAndLoss> anyProfitAndLoss = profitAndLosses.stream().findAny();
        return anyProfitAndLoss.get();
    }

    /**
     * 功能：删除订单
     */
    public ProfitAndLoss delete(SimpleRequest request) {
        isVaild(request, profitAndLossRepository);
        ProfitAndLoss profitAndLoss = request.getO(ProfitAndLoss.class);

        profitAndLoss.setState("delete");
        profitAndLoss.setDeleteId(getPerson(request));
        profitAndLoss.setDeleteDate(new Date());

        // 删除占用
        profitAndLoss.getProfitAndLossItems().stream().forEach(
                item -> {
                    // 可用数还原
                    setProductAvailable(profitAndLoss, item, 1, "报损单更新还原");

                    // 批次冻结数还原
                    setInventoryBatch(item, -1);
                });
        return profitAndLossRepository.saveAndFlush(profitAndLoss);
    }

    public ProfitAndLoss approve(SimpleRequest request) {
        isVaild(request, profitAndLossRepository);
        ProfitAndLoss profitAndLoss = request.getO(ProfitAndLoss.class);
        profitAndLoss.setState("approve");
        profitAndLoss.setApproveUid(getPerson(request));
        profitAndLoss.setDateApprove(new Date());
        profitAndLoss = profitAndLossRepository.saveAndFlush(profitAndLoss);

        // 写入账务逻辑
        JSONObject voucherParam = new JSONObject();
        voucherParam.put("id", profitAndLoss.getId());
        SpringManager.getBean(OutboxSubscriberService.class)
                .commonSendRequest("local:productLossService.writeVoucherOutbox", profitAndLoss.getId(), voucherParam);
        return profitAndLoss;
    }


    // 写入凭证账单
    public void writeVoucher(String id) {
        if (SpringManager.getBean(VoucherRepository.class).findOne(JpaDsl.toCriteriaByEq("source", id)).orElse(null) != null) throw new RuntimeException("请勿重复执行！");
        ProfitAndLoss profitAndLoss = profitAndLossRepository.findOne(id);

        //写入保管数
        profitAndLoss.getProfitAndLossItems().forEach( item ->
                productAccountService.setProductCustody(item.getProduct(),
                        profitAndLoss.getLocation(),
                        item.getUom(), -1 * Math.abs(item.getQty()),
                        profitAndLoss.getInnerBusinessType(), profitAndLoss.getId(), item.getId(),
                        "报损单审核", profitAndLoss.getCompany())
                );

        // 写入账务逻辑
        Voucher voucher = getVoucher(profitAndLoss);
        SpringManager.getBean(VoucherService.class).save(voucher);
        SpringManager.getBean(InvoiceService.class).save(getInvoiced(voucher));
    }

    @OutboxSubscriber(id="local:productLossService.writeVoucherOutbox", name="报损写入凭证")
    public void writeVoucherOutbox(OutboxPayloadWrapper outboxPayloadWrapper) {
        this.writeVoucher(JSONObject.parseObject(outboxPayloadWrapper.getPayload()).getString("id"));
    }

    /***
     * 验证数据合法性
     */
    private Model isVaild(SimpleRequest request, CustomRepository repository) {
        Model oldModel = (Model) repository.findOne((String) request.get("id"));
        if (oldModel == null) throw new RuntimeException("订单【" + request.get("id") + "】在系统中不存在。");
        if (oldModel.getVersion() != (int) request.get("version")) throw new RuntimeException("记录已更新或已删除，请重新提交！");
        return oldModel;
    }

    /**
     * 写入商品可用数
     *
     * @param profitAndLoss 损益单
     * @param type          传入 1 或者 -1
     */
    private void setProductAvailable(ProfitAndLoss profitAndLoss, ProfitAndLossItem lossItem, int type, String note) {
        productAccountService.setProductAvailable(lossItem.getProduct(),
                profitAndLoss.getLocation(), lossItem.getUom(),
                type * lossItem.getQty(),
                profitAndLoss.getInnerBusinessType(),
                profitAndLoss.getId(), lossItem.getId(),
                note, profitAndLoss.getLocation().getCompany());
    }

    /**
     * 增加（减少）商品价值（商品批次） 的冻结数量
     * @param item 明细
     * @param type 1 增加  -1 减少
     */
    private void setInventoryBatch(ProfitAndLossItem item, int type){
        // 查询批次信息
//        InventoryBatch inventoryBatch = inventoryBatchRepository.findOne(item.getInventoryBatch().getId());
        //  增加（减少）冻结数量
        // inventoryBatch.changeFrostQty(type * item.getQty());
        inventoryService.changeFrostQty(item.getInventoryBatch(), item.getUom(),type * item.getQty(),item.getProfitAndLoss().getCompany(),item.getProfitAndLoss().getInnerBusinessType(),item.getProfitAndLoss().getId(),item.getId(),"損溢單" );
    }

    /**
     * 保管数
     * @param profitAndLoss
     */
    private void setProductCustody(ProfitAndLoss profitAndLoss) {
        profitAndLoss.getProfitAndLossItems().stream().forEach(
                lossItem -> {
                    productAccountService.setProductCustody(lossItem.getProduct(),
                            profitAndLoss.getLocation(),
                            lossItem.getUom(), -1 * Math.abs(lossItem.getQty()),
                            profitAndLoss.getInnerBusinessType(), profitAndLoss.getId(), lossItem.getId(),
                            "报损单审核", profitAndLoss.getCompany());
                });
    }

    /**
     * 查找可报损商品批次
     *
     * @param simpleRequest
     * @return Page<InventoryBatch>
     */
    public Page<InventoryBatch> findInventory(SimpleRequest simpleRequest) {
        String productId = simpleRequest.get("productId");
        String locationId = simpleRequest.get("locationId");
        // 查找商品库存帐的可用数是否大于0
        Stock stock = stockRepository.findFirstByProductIdAndLocationIdAndUsableQtyGreaterThan(productId, locationId, 0);
        // 获取可用数
        double usableQty = Optional.ofNullable(stock).map((s) -> s.getUsableQty()).orElseThrow(() -> new RuntimeException("商品库存帐不满足报损条件！"));
        String companyId = simpleRequest.get("companyId");
        // 获取结存大于冻结的批次
        Page<InventoryBatch> inventoryBatchPage = inventoryBatchRepository.findReturnableByCompanyIdAndProductId(companyId, productId, PageRequest.of(simpleRequest.get("page"), simpleRequest.get("size")));
        inventoryBatchPage.getContent().forEach(
                (inventoryBatch) -> {
                    inventoryBatch.setUsableQty(usableQty);
                    inventoryBatch.setCanReturnQty(usableQty > inventoryBatch.getBalanceQty() - inventoryBatch.getFrostQty() ? inventoryBatch.getBalanceQty() - inventoryBatch.getFrostQty() : usableQty);
                }
        );
        return inventoryBatchPage;
    }

    public Page<Map> qryProductLossUsable(SimpleRequest simpleRequest) {
        JSONObject getCondition = this.getCondition(simpleRequest.getJO());
        String locationId = getCondition.getString("locationId");
        Page<Map> mapPage;
        if (getCondition.get("productId") != null) {
            mapPage = inventoryBatchRepository.findProductLossProduct(locationId, getCondition.getString("productId"), /*"distribution",*/ PageRequest.of(simpleRequest.get("page"), simpleRequest.get("size")));
        } else {
            mapPage = inventoryBatchRepository.findProductLoss(locationId, /*"distribution",*/ PageRequest.of(simpleRequest.get("page"), simpleRequest.get("size")));
        }
        return mapPage;
    }


    // 导入
    public JSONObject importLogic(SimpleRequest request) {
        JSONObject getJO = request.getJO();
        JSONObject importData = getJO.getJSONObject("importData");
        JSONArray titleColumnArray = new JSONArray();
        String locationId = getJO.getJSONObject("model").getJSONObject("location").getString("id");
        String key = appendImportReturn(locationId, importData, titleColumnArray);
        JSONObject returnData = new JSONObject();
        returnData.put(key, titleColumnArray);
        return returnData;
    }

    // 拼接导入数据
    private String appendImportReturn(String locationId, JSONObject importData, JSONArray titleColumnArray) {
        AtomicReference key = new AtomicReference();
        importData.forEach((k, v) -> {
            key.set(k);
            JSONArray importDataValueArray = (JSONArray) v;
            for (int i = 0; i < importDataValueArray.size(); i++) {
                JSONArray iValueArray = (JSONArray) importDataValueArray.get(i);
                if (iValueArray.isEmpty()) continue;

                String productId = iValueArray.get(0).toString();
                String qty = iValueArray.get(1).toString();
                JSONArray newDataArray = new JSONArray();
                if (i == 0) {
                    newDataArray.add("序号");
                    newDataArray.add("订单行号");
                    newDataArray.add("来源单明细单号");
                    newDataArray.add("商品");
                    newDataArray.add("单位");
                    newDataArray.add("进项税率");
                    newDataArray.add("税率");
                    newDataArray.add("单价");
                    newDataArray.add("损数量");
                    newDataArray.add("金额");
                    newDataArray.add("批次号");
                    newDataArray.add("可报损数");
                    newDataArray.add("备注");
                    titleColumnArray.add(newDataArray);
                    continue;
                }

                List<Map> inventoryBatchs = inventoryBatchRepository.findProductLossProduct(locationId, productId);

                if (inventoryBatchs.size() == 0) throw new RuntimeException("未找到对应批次，商品：" + productId);
                Map inventoryBatch = inventoryBatchs.get(0);
                newDataArray.add("");
                newDataArray.add("");
                newDataArray.add("");
                newDataArray.add(MapUtil.mapper("id", productId, "name", inventoryBatch.get("PRODUCT_NAME")));
                newDataArray.add(MapUtil.mapper("id", inventoryBatch.get("PRODUCT_UOM_ID"), "name", inventoryBatch.get("PRODUCT_UOM_NAME")));
                newDataArray.add(MapUtil.mapper("id", inventoryBatch.get("TAX_ID"), "name", inventoryBatch.get("TAX_NAME")));
                newDataArray.add(inventoryBatch.get("TAX_COST_PRICE"));
                newDataArray.add(qty);
                newDataArray.add(Double.valueOf(qty) * Double.valueOf(inventoryBatch.get("TAX_COST_PRICE").toString()));
                newDataArray.add(MapUtil.mapper("id", inventoryBatch.get("INVENTORY_BATCH_ID")));
                newDataArray.add(inventoryBatch.get("USABLE_QTY"));
                newDataArray.add("");
                titleColumnArray.add(newDataArray);
            }
        });
        return key.get().toString();
    }


    private JSONObject getCondition(JSONObject getJO) {
        JSONObject condition = new JSONObject();
        if (getJO.get("productId") != null) {
            condition.put("productId", getJO.getString("productId"));
            condition.put("locationId", getJO.getString("locationId"));
            condition.put("companyId", getJO.getString("companyId"));
            return condition;
        }
        if (getJO.get("key") != null) {
            condition.put("productId", getJO.get("key"));
        }
        JSONObject domainObj = getJO.getJSONObject("domain");
        JSONObject groupObj = getJO.getJSONObject("group");
        JSONArray filtersArray = new JSONArray();
        if (domainObj != null) {
            filtersArray = getJO.getJSONObject("domain").getJSONArray("filters");
        } else if (groupObj != null) {
            filtersArray = groupObj.getJSONArray("filters");
        }
        for (int i = 0, iLen = filtersArray.size(); i < iLen; i++) {
            JSONObject filtersObj = filtersArray.getJSONObject(i);
            String field = filtersObj.getString("field");
            String value = filtersObj.getString("value");
            switch (field) {
                case "location":
                    condition.put("locationId", value);
                    break;
            }
        }
        if (getJO.getJSONArray("condition") != null) {
            JSONArray conditionArray = getJO.getJSONArray("condition");
            for (int i = 0, iLen = conditionArray.size(); i < iLen; i++) {
                JSONObject conditionObj = conditionArray.getJSONObject(i);
                String field = conditionObj.getString("field");
                String value = conditionObj.getString("value");
                switch (field) {
                    case "product":
                        condition.put("productId", value);
                        break;
                }
            }
        }
        return condition;
    }

    private Voucher getVoucher(ProfitAndLoss productLoss) {
//        ProfitAndLoss productLoss = profitAndLossRepository.findOne(oldProductLoss.getId());
        Voucher voucher = new Voucher();
        // 主表信息
        voucher.setCompany(productLoss.getCompany());//公司
        voucher.setDept(productLoss.getLocation().getDepot().getDept());//部门
        voucher.setPartner(productLoss.getCompany().getParty().getPatyRoleByType("Customer"));//合作伙伴编码

        voucher.setInventoryMode(InventoryMode.IM_OWN);     //设置库存模式：自有
        voucher.setAccountingMethod(AccountingMethod.AM_PURCHASE);  // 核算方式：经销

        // 凭证模板必配字段
        voucher.setBusinessType(BusinessType.SALE_ORDER);//业务单据类型
        voucher.setOrigin(StringUtil.isEmpty(productLoss.getSource()) ? productLoss.getId() : productLoss.getSource());//源单据
        voucher.setOriginBusinessType(StringUtil.isEmpty(productLoss.getSourceBusinessType()) ? productLoss.getInnerBusinessType() : productLoss.getSourceBusinessType());//来源单据类型
        voucher.setSource(productLoss.getId());//来源单据
        voucher.setSourceBusinessType(productLoss.getInnerBusinessType());//来源单据类型

        // 从表信息
        Set<VoucherItem> voucherItems = new HashSet();
        productLoss.getProfitAndLossItems().forEach(items -> {
            VoucherItem voucherItem = new VoucherItem();

            voucherItem.setVoucher(voucher);//凭证单号
            voucherItem.setProduct(items.getProduct());//商品
            Tax tax = SpringManager.getBean(TaxService.class).getSaleTax(voucher.getCompany(), items.getProduct());
            voucherItem.setTax(tax);

            voucherItem.setOwner(productLoss.getCompany());//物权公司

            // 设置为销售的单位以及单价
            voucherItem.setUom(items.getUom());//计量单位编码
            voucherItem.setProductQty(items.getQty());//数量
            voucherItem.setPriceUnit(/*items.getPriceUnit()*/0);//含税单价

            voucherItem.setOriginItemId(items.getId());//源单据明细单号
            voucherItem.setSourceItemId(items.getSourceItemId());//来源单明细单号
            voucherItem.setSupplier(items.getInventoryBatch().getSupplier());//供应商编码
            voucherItem.setInventoryBatch(items.getInventoryBatch());//商品价值库存内码

            voucherItems.add(voucherItem);
        });
        voucher.setVoucherItems(voucherItems);
        return voucher;
    }

    /**
     * 创建账单
     * @param voucher
     */
    private Invoice getInvoiced(Voucher voucher) {
        Invoice invoice = new Invoice();
        invoice.setCreator(voucher.getCreator());
        invoice.setCompany(voucher.getCompany());
        invoice.setPartner(voucher.getPartner());
        invoice.setDept(voucher.getDept());
        invoice.setContract(voucher.getContract());
        invoice.setInventoryMode(voucher.getInventoryMode());
        invoice.setAccountingMethod(voucher.getAccountingMethod());
        invoice.setOriginBusinessType(voucher.getOriginBusinessType());
        invoice.setOrigin(voucher.getOrigin());
        invoice.setSourceBusinessType(voucher.getInnerBusinessType());
        invoice.setSource(voucher.getId());
        invoice.setCustomerExpand(voucher.getCustomerExpand());
        invoice.setInvoiceItems(voucher.getVoucherItems().stream().map(voucherItem -> {
            InvoiceItem invoiceItem = new InvoiceItem();
            invoiceItem.setProduct(voucherItem.getProduct());
            invoiceItem.setPriceUnit(voucherItem.getPriceUnit());
            invoiceItem.setQty(voucherItem.getProductQty());
            invoiceItem.setUom(voucherItem.getUom());
            invoiceItem.setTax(voucherItem.getTax());
            invoiceItem.setOriginItemId(voucherItem.getOriginItemId());
            invoiceItem.setSourceItemId(String.valueOf(voucherItem.getId()));
            return invoiceItem;
        }).collect(Collectors.toSet()));
        return invoice;
    }

    /************************************************OA*********************************************************************/
    /**
     * 发起OA审批
     * @param request
     * @return
     */
    public ProfitAndLoss initiateOA(SimpleRequest request) {
        ProfitAndLoss profitAndLoss = request.getO(ProfitAndLoss.class);
        profitAndLoss.setState("OA");

        String creatorId = profitAndLoss.getCreator();
        profitAndLoss.setOAUser(SpringManager.getBean(UserRepository.class).findAll(JpaDsl.toCriteriaByEq("person.id", request.getPersonId())).get(0).getId().toString());
        Map map = MapUtil.mapped(profitAndLoss);
        SimpleRequest simpleRequest = new SimpleRequest();
        simpleRequest.setData(map);

        return this.update(simpleRequest);
    }

    public void OADelete(SimpleRequest simpleRequest) {
        ProfitAndLoss profitAndLoss = simpleRequest.getO(ProfitAndLoss.class);
        simpleRequest.setData(MapUtil.mapped(profitAndLossRepository.findOne(profitAndLoss.getId())));
        this.delete(simpleRequest);
    }

    /**
     * 禁止代管库做损溢
     * @param request
     * @return
     */
    public int forbidAgency(SimpleRequest request){
        PartyRole company = partyRoleRepository.findOne(request.getJO().getString("id"));
        List<PartyRole> companyList = partyRoleRepository.findAll(JpaDsl.toCriteriaByEq("party.id", (company.getParty().getId() + "")));
        companyList.forEach(companyItem -> {
            if ("Warehousing".equals(companyItem.getRole().getId())) throw new RuntimeException("代管库商品不能制定损益单，需要报损商品请调拨到车宝仓库再做损益！");
        });
        return 1;
    }

    /**
     * 损益单增加展示审核备注
     * @param request
     * @return
     */
    public String approveLog(SimpleRequest request){
        JSONObject jo = request.getJO();
        String id = jo.getString("id");
        ProfitAndLoss profitAndLoss = profitAndLossRepository.findOne(id);
        //写入过无需再次写入
        if (StringUtils.isNotEmpty(profitAndLoss.getApproveNote())) return profitAndLoss.getApproveNote();
        //获取审核备注
        List<ApesLogger> apesLoggers = apesLoggerRepository.findByBusinessKeyAndOperateLike(id, "财务审批%");
        if (apesLoggers.isEmpty()) return "";
        ApesLogger apesLogger = apesLoggers.stream().filter(item -> StringUtils.isNotEmpty(item.getOperate())).findAny().get();
        profitAndLoss.setApproveNote(apesLogger.getRemark());
        profitAndLossRepository.saveAndFlush(profitAndLoss);
        return apesLogger.getRemark();
    }
}
